前置知识
开始之前补充一些知识
String.prototype.codePointAt
方法,返回 一个 Unicode 编码点值的非负整数,如果在指定的位置没有元素则返回undefined
示例:
1 | str.codePointAt(pos) |
- ast(抽象语法树)
可以在这个网站尝试代码转成ast :https://astexplorer.net/
例如:
1 | console.log('hello qhw') |
转换成json的结果
1 | { |
ast是由单个或多个节点组成的,节点在不同层级有相似的结构。
节点中的type字段表示节点的类型。
- Babylon,仓库被移动了,它的新身份的是
@babel/parser
。它将代码解析成ast,在解析过程中有两个阶段:词法分析和语法分析,下面会说。
- visitor
当Babel处理一个节点时,是以访问者的形式获取节点信息,并进行相关操作,这种方式是通过一个visitor对象来完成的,在visitor对象中定义了对于各种节点的访问函数,这样就可以针对不同的节点做出不同的处理。我们编写的Babel插件其实也是通过定义一个实例化visitor对象处理一系列的AST节点来完成我们对代码的修改操作。
- path
从上面的visitor对象中,可以看到每次访问节点方法时,都会传入一个path参数,这个path参数中包含了节点的信息以及节点和所在的位置,以供对特定节点进行操作。具体来说Path 是表示两个节点之间连接的对象。这个对象不仅包含了当前节点的信息,也有当前节点的父节点的信息,同时也包含了添加、更新、移动和删除节点有关的其他很多方法。具体地,Path对象包含的属性和方法主要如下:
1 | ── 属性 |
- state
state是visitor对象中每次访问节点方法时传入的第二个参数。state就是一系列状态的集合,包含诸如当前plugin的信息、plugin传入的配置参数信息,甚至当前节点的path信息也能获取到,当然也可以把babel插件处理过程中的自定义状态存储到state对象中。
- scopes作用域
这里的作用域其实跟js说的作用域是一个道理,也就是说babel在处理AST时也需要考虑作用域的问题,比如函数内外的同名变量需要区分开来。
通过一个示例来看过程
1 | const { |
解析阶段
将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过Babylon实现的。在解析过程中有 词法分析 和 语法分析。词法分析阶段把字符串形式的代码转换为令牌,令牌类似于AST中节点;而语法分析阶段则会把令牌转换成 AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构。
- 词法分析生成token
从parse(code)
方法调用进入,断点走到这个方法
getTokenFromCode
方法,根据不同的Unicode编码值,进行不同的处理
1 | getTokenFromCode(code) { |
看一下readWord
方法
进入readWord1
方法,比如console.log
解析,就先拿console
,然后返回
- 语法分析
createIdentifier
方法,创建节点
finishNode
方法
finishNodeAt
方法
解析出的ast大概是这样的
1 | { |
转换阶段
在这个阶段,Babel接受得到AST并通过babel-traverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。这部分也是Babel插件介入工作的部分。
进入traverse
方法调用
进入traverse.node
方法调用,从根开始的深度优先遍历
进入context.visit
方法调用,根据nodes是否是数组,调用不同的visit方法
进入visitSingle
方法
进入visitQueue
方法
这里调用enter
从opts里拿到enter的数组
遍历fns,并调用
进入enter函数调用
打印出来的enter的路径,也就是深度遍历的顺序。
生成阶段
将经过转换的AST通过babel-generator再转换成js代码,过程就是深度优先遍历整个AST,然后构建可以表示转换后代码的字符串。
进入generate
方法
进入gen.generate
调用,this.ast
是在new Generator(ast, opts, code)
的时候挂载在实例上的
进入generate
方法
在print
方法里做的对节点解析并且push
到_buf
数组里
最后调用this._buf.get
方法,把字符串合并